#include <iostream>
#include <cstring>
#include <string>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <memory>

#include <pthread.h>

#include "thread.hpp"

// 发现：无论是多线程读还是写，用的sock都是同一个，sock代表的就是文件，
// udp是全双工的 --> 可以同时进行接收和发送而不受干扰

uint16_t serverport = 0;
std::string serverip;

static void usage(void* proc){
    std::cout << "\nusage: " << proc << " serverip serverport\n" << std::endl;
}

// 开一个线程用来接收信息
static void* udpRecv(void* args){
    int sock = *(int*)((ThreadData*)args)->args_;
    std::string name = ((ThreadData*)args)->name_;

    char buffer[1024];
    while(true){
        memset(buffer, 0, sizeof(buffer));
        // 创建一个临时的 struct sockaddr_in temp 用来接收是谁给我们发送的信息，虽然我们直到是 serverip 和 serverport
        // 但是可能是 server 端的不同进程在给我们发送信息
        struct sockaddr_in temp;
        bzero(&temp, sizeof(temp));
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&temp, &len);
        if(s > 0){
            buffer[s] = 0;
            std::cout << buffer << std::endl;
        }
    }

    return nullptr;
}

// 开一个线程用来发送信息
static void* udpSend(void* args){
    // 直接创建一个子线程用来父子间的通信
    pthread_t recv;
    pthread_create(&recv, nullptr, udpRecv, (void*)args);

    int sock = *(int*)((ThreadData*)args)->args_;
    std::string name = ((ThreadData*)args)->name_;

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    // 要发送的信息，不过 c 式的接口要的是 char*类型的变量的哦。可以 message..c_str()
    std::string message;
    while(true){
        std::cout << "请输入你的信息# ";
        std::getline(std::cin, message);  // getline 函数是 c++ 里面才有的哦
        // 提供一个退出的方式
        if(message == "quit") break;

        // 当client首次发送信息给服务器的时候，OS会自动给client bind 它的IP和port的哦
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
    }

    return nullptr;
}

// ./udpClient 127.0.0.1 8080
int main(int argc, char* argv[]){
    if(argc != 3){
        usage(argv[0]);
        exit(1);
    }
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0){
        std::cerr << "socket error" << std::endl;
        exit(2);
    }
    serverip = argv[1];
    serverport = atoi(argv[2]);
    // client关于要不要bind? 是要的，但是不是我们自己去bind，OS背后会帮你bind的

    // 1. 因为下面的两个进程之间不是父子进程的关系，所以不能不 mkfifo fifo 创建的 fifo(命名管道来通信的哦)。
    // 2. udpSend 函数可以创建一个子线程用来执行 udpRecv 函数, 就可以创建 fifo 用来父子进程间的通信了。 

    // sock 是用于 client 通信的，所以得传递过去的哦
    std::unique_ptr<Thread> sender(new Thread(1, udpSend, (void*)&sock));
    // std::unique_ptr<Thread> recver(new Thread(2, udpRecv, (void*)&sock));

    sender->start();
    // recver->start();

    sender->join();
    // recver->join();

    close(sock);

//////////////////////////////////////////////////////////////
    // struct sockaddr_in server;
    // memset(&server, 0,sizeof(server));
    // server.sin_family =AF_INET;
    // server.sin_port = htons(serverport);
    // server.sin_addr.s_addr = inet_addr(serverip.c_str());

    // std::string message;
    // char buffer[1024];
    // while(true){
    //     // 多线程
    //     std::cout << "请输入你的信息# ";
    //     std::getline(std::cin, message);
    //     if(message == "quit") break;
    //     // 当client首次发送信息给server的时候，OS是会自动给client bind它的IP和port的
    //     sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));

    //     struct sockaddr_in temp;
    //     socklen_t len = sizeof(temp);
    //     ssize_t s = recvfrom(sock, buffer,sizeof(buffer), 0, (struct sockaddr*)&temp, sizeof(temp));
    //     if(s > 0){
    //         buffer[s] = 0;
    //         std::cout << "server echo# " << buffer << std::endl;
    //     }
    // }

    return 0;
}

